﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Editor;
using EnvDTE;
using Microsoft.VisualStudio;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.ExtensionManager;

namespace Util
{
    public static class VSUtil
    {
        public static IWpfTextView ToWpfTextView(this IVsWindowFrame vsWindowFrame)
        {
            IWpfTextView wpfTextView = null;
            var vsTextView = VsShellUtilities.GetTextView(vsWindowFrame);

            if (vsTextView != null)
            {
                object textViewHost;
                Guid guidTextViewHost = Microsoft.VisualStudio.Editor.DefGuidList.guidIWpfTextViewHost;
                if (((IVsUserData)vsTextView).GetData(ref guidTextViewHost, out textViewHost) == VSConstants.S_OK &&
                    textViewHost != null)
                {
                    wpfTextView = ((IWpfTextViewHost)textViewHost).TextView;
                }
            }

            return wpfTextView;
        }

        // From http://stackoverflow.com/questions/2413530/find-an-ivstextview-or-iwpftextview-for-a-given-projectitem-in-vs-2010-rc-exten
        /// <summary>
        /// Returns an IWpfTextView for the given file path, if the given file is open in Visual Studio.
        /// </summary>
        /// <param name="filePath">Full Path of the file you are looking for.</param>
        /// <returns>The IWpfTextView for this file, if it is open, null otherwise.</returns>
        public static IWpfTextView GetIWpfTextView(string filePath)
        {
            var dte2 = (EnvDTE80.DTE2)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(Microsoft.VisualStudio.Shell.Interop.SDTE));
            Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte2;
            using (Microsoft.VisualStudio.Shell.ServiceProvider serviceProvider = new Microsoft.VisualStudio.Shell.ServiceProvider(sp))
            {
                Microsoft.VisualStudio.Shell.Interop.IVsUIHierarchy uiHierarchy;
                uint itemID;
                Microsoft.VisualStudio.Shell.Interop.IVsWindowFrame windowFrame;
                if (Microsoft.VisualStudio.Shell.VsShellUtilities.IsDocumentOpen(serviceProvider, filePath, Guid.Empty,
                                                out uiHierarchy, out itemID, out windowFrame))
                {
                    // Get the IVsTextView from the windowFrame.
                    return windowFrame.ToWpfTextView();
                }

                return null;
            }
        }

        public static IWpfTextView GetIWpfTextView(this Document doc)
        {
            return GetIWpfTextView(doc.FullName);
        }

        public static IWpfTextView GetCurrentIWpfTextView(this EnvDTE.DTE dte)
        {
            return dte.ActiveDocument.GetIWpfTextView();
        }

        public static IVsExtensionManager GetExtensionManager()
        {
            return GetService<IVsExtensionManager, SVsExtensionManager>(GlobalServiceProvider);
        }
        public static IInstalledExtension GetThisExtension(string identifier)
        {
            return GetExtensionManager().GetInstalledExtension(identifier);
        }
        public static string GetThisExtensionDirectory(string identifier)
        {
            string dir = GetThisExtension(identifier).InstallPath;
            if (dir.EndsWith(@"\"))
            {
                dir = dir.Substring(0, dir.Length - 1);
            }
            return dir;
        }

        public static IVsRunningDocumentTable GetRunningDocumentTable()
        {
            return GetService<IVsRunningDocumentTable, SVsRunningDocumentTable>(GlobalServiceProvider);
        }

        public static IComponentModel GetComponentModel()
        {
            return GetService<IComponentModel, SComponentModel>(GlobalServiceProvider);
        }

        #region Helpers - GetService
        private static Microsoft.VisualStudio.OLE.Interop.IServiceProvider globalServiceProvider;
        private static Microsoft.VisualStudio.OLE.Interop.IServiceProvider GlobalServiceProvider
        {
            get
            {
                if (globalServiceProvider == null)
                {
                    globalServiceProvider = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)Package.GetGlobalService(
                        typeof(Microsoft.VisualStudio.OLE.Interop.IServiceProvider));
                }

                return globalServiceProvider;
            }
        }

        private static object GetService(
            Microsoft.VisualStudio.OLE.Interop.IServiceProvider serviceProvider, Guid guidService, bool unique)
        {
            var guidInterface = VSConstants.IID_IUnknown;
            var ptr = IntPtr.Zero;
            object service = null;

            if (serviceProvider.QueryService(ref guidService, ref guidInterface, out ptr) == 0 &&
                ptr != IntPtr.Zero)
            {
                try
                {
                    if (unique)
                    {
                        service = Marshal.GetUniqueObjectForIUnknown(ptr);
                    }
                    else
                    {
                        service = Marshal.GetObjectForIUnknown(ptr);
                    }
                }
                finally
                {
                    Marshal.Release(ptr);
                }
            }

            return service;
        }

        private static TServiceInterface GetService<TServiceInterface, TService>(
            Microsoft.VisualStudio.OLE.Interop.IServiceProvider serviceProvider)
            where TServiceInterface : class
            where TService : class
        {
            return (TServiceInterface)GetService(serviceProvider, typeof(TService).GUID, false);
        }

        private static TServiceInterface GetMefService<TServiceInterface>() where TServiceInterface : class
        {
            TServiceInterface service = null;
            var componentModel = GetService<IComponentModel, SComponentModel>(GlobalServiceProvider);

            if (componentModel != null)
            {
                service = componentModel.GetService<TServiceInterface>();
            }

            return service;
        }
        #endregion
    }
}
